#ifndef __IDB__HEADER__
#define __IDB__HEADER__
#include "../include/idb.h"
#endif

#ifndef _AST_HEADER_
#define _AST_HEADER_
#include "../include/ast.h"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>


FileHeaderStructPoint idb_getFileHeader(FILE * fp){
    fseek(fp,0,SEEK_SET);
    FileHeaderStructPoint fhsp = ( FileHeaderStructPoint)malloc(sizeof (FileHeaderStruct));
    fread(fhsp,sizeof (FileHeaderStruct),1,fp);
    return fhsp;
}

void idb_writeBackFileHeader(FILE * fp,FileHeaderStructPoint fhsp){
    fseek(fp,0,SEEK_SET);
    fwrite(fhsp,sizeof(FileHeaderStruct),1,fp);
}

void idb_readKeyStruct(FILE * fp,int number,KeyStructPoint keys){
    fseek(fp,sizeof(FileHeaderStruct),SEEK_SET);
    for(int i = 0 ;i < number;i++){
        fread(keys + i,sizeof(KeyStruct),1,fp);
    }
}

bool idb_create(FILE * fp,CreateFieldStructPoint csfp){

    FileHeaderStruct  fhs;

    fseek(fp,0,SEEK_SET);
    fwrite(&fhs,sizeof(FileHeaderStruct),1,fp);

    fhs.firstAvailableSpace = 0;
    fhs.recordNumber = 0;

    int count = 0;
    int recordSize = 0;
    CreateFieldStructPoint  tmp = csfp;
    while(tmp != NULL){
        count ++;
        KeyStruct key;
        key.flag = tmp->type;
        switch (key.flag){
            case FIELD_INT:
                recordSize += 4;
                break;
            case FIELD_DOUBLE:
                recordSize += 8;
                break;
            case FIELD_STRING:
                key.length = tmp->length;
                recordSize += tmp->length;
                break;
            default:
                printf("error: can not recognize the type of '%s'!\n",tmp->name);
                return false;
        }

        strcpy(key.name,tmp->name);
        fwrite(&key,sizeof(KeyStruct),1,fp);
        tmp = tmp->next;
    }

    fhs.keyNumber = count;
    fhs.headerLength = count * sizeof(KeyStruct) + sizeof(FileHeaderStruct);
    // 4 is DataItemHeader
    fhs.recordSize = recordSize + 4;
    fseek(fp,0,SEEK_SET);
    fwrite(&fhs,sizeof(FileHeaderStruct),1,fp);

    return true;
}

bool idb_insert(FILE * fp,InsertStructPoint isp){

    FileHeaderStructPoint  fhsp = idb_getFileHeader(fp);

    void * data = calloc(fhsp->recordSize,1);

    * (int *) data = 0;

    KeyStruct keys[fhsp->keyNumber];
    idb_readKeyStruct(fp,fhsp->keyNumber,keys);

    if(isp->fields == NULL){
        char * idx = (char *)data + 4;
        InsertItemStructPoint tmp = isp->items;
        for(int i = 0;i < fhsp->keyNumber;i++){
            if(tmp == NULL){
                free(data);
                free(fhsp);
                printf("error: your values number can not match the %d number of keys!\n",fhsp->keyNumber);
                return false;
            }
            if(keys[i].flag != tmp->value->type){
                free(data);
                free(fhsp);
                printf("error: can not match the %d value with the key %s!\n",i,keys[i].name);
                return false;
            }
            if(keys[i].flag == FIELD_INT){
                *(int *)idx = tmp->value->value.i;
                idx += 4;
            }else if(keys[i].flag == FIELD_DOUBLE){
                *(double *)idx = tmp->value->value.d;
                idx += 8;
            }else{
               if(strlen(tmp->value->value.m_string) + 1 > keys[i].length){
                   free(data);
                   free(fhsp);
                   printf("error: the string '%s' in your values is too long which you defined the max length is %d in key %s!\n",
                          tmp->value->value.m_string ,keys[i].length,keys[i].name);
                   return false;
               }
               strcpy(idx,tmp->value->value.m_string);
               idx += keys[i].length;
            }
            tmp = tmp->next;
        }
    }else{
        char * o_idx = (char *)data;
        InsertItemStructPoint tmp = isp->items;
        InsertFieldStructPoint ftmp = isp->fields;
        int offset[fhsp->keyNumber];

        offset[0] = 4;
        for(int i = 1;i < fhsp->keyNumber;i++){

            if(keys[i - 1].flag == FIELD_INT){
                offset[i] = offset[i - 1] + 4;
            }else if(keys[i - 1].flag == FIELD_DOUBLE){
                offset[i] = offset[i - 1] + 8;
            }else{
                offset[i] = offset[i - 1] + keys[i - 1].length;
            }
        }

        while(ftmp != NULL){
            if(tmp == NULL)
            {
                free(data);
                free(fhsp);
                printf("error: '%s', you values number can not match your fields number!\n",ftmp->fieldName);
                return false;
            }
            int i = 0;
            for(; i < fhsp->keyNumber;i++){
                if(!strcmp(keys[i].name, ftmp->fieldName)){
                    break;
                }
            }

            if(i == fhsp->keyNumber){
                free(data);
                free(fhsp);
                printf("error: '%s', can not find the key in your fields!\n",ftmp->fieldName);
                return false;
            }

            char * idx = o_idx + offset[i];

            if(keys[i].flag != tmp->value->type){
                free(data);
                free(fhsp);
                printf("error: can not match the %d value with the key %s!\n",i,keys[i].name);
                return false;
            }
            if(keys[i].flag == FIELD_INT){
                *(int *)idx = tmp->value->value.i;
            }else if(keys[i].flag == FIELD_DOUBLE){
                *(double *)idx = tmp->value->value.d;
            }else{
                if(strlen(tmp->value->value.m_string) + 1 > keys[i].length){
                    free(data);
                    free(fhsp);
                    printf("error: the string '%s' in your values is too long which you defined the max length is %d in key %s!\n",
                           tmp->value->value.m_string ,keys[i].length,keys[i].name);
                    return false;
                }
                strcpy(idx,tmp->value->value.m_string);
            }
            ftmp = ftmp->next;
            tmp = tmp->next;
        }

        if(tmp != NULL){
            free(data);
            free(fhsp);
            printf("error: '%s', you values number can not match your fields number!\n",ftmp->fieldName);
            return false;
        }

    }


    int freeSpaceIndex = fhsp->firstAvailableSpace;

    if(freeSpaceIndex == 0){
        fhsp->recordNumber += 1;
        idb_writeBackFileHeader(fp,fhsp);

        fseek(fp,0,SEEK_END);
        fwrite(data, fhsp->recordSize, 1, fp);

        free(fhsp);
        free(data);
        return true;
    }

    void * buffer = malloc(fhsp->recordSize);
    fseek(fp,fhsp->headerLength + (freeSpaceIndex - 1) * fhsp->recordSize,SEEK_SET);
    fread(buffer,fhsp->recordSize,1,fp);

    fhsp->firstAvailableSpace =  *(int *)buffer == -1 ? 0 : *(int *)buffer;
    fhsp->recordNumber += 1;
    idb_writeBackFileHeader(fp,fhsp);
    fseek(fp,fhsp->headerLength + (freeSpaceIndex - 1) * fhsp->recordSize,SEEK_SET);
    fwrite(data,fhsp->recordSize,1,fp);

    free(fhsp);
    free(data);
    return true;
}

bool selectItemMatch(KeyStructPoint keys,int * offset, void * data, int number, ConditionStructPoint con){
    if(con == NULL){
        return true;
    }

    if(con->left->cp == NO_CMP){
        char * field = con->left->value->value.m_string;
        DataStructPoint val = con->right->value;
        int i =0;
        for(;i < number;i++){
            if(!strcmp(field,keys[i].name)){
                break;
            }
        }
        if(i == number){
            return error;
        }

        if(keys[i].flag == FIELD_INT){
            if(val->type != FIELD_INT){
                printf("can't match the type.\n");
                return error;
            }
            int left = * (int *)((char *) data + offset[i]);
            if(con->cp == LE){
                if(left <= val->value.i){
                    return true;
                }
            }else if(con -> cp == LT){
                if(left < val->value.i){
                    return true;
                }
            }else if(con -> cp == NE){
                if(left != val->value.i){
                    return true;
                }
            }else if(con -> cp == GE){
                if(left >= val->value.i){
                    return true;
                }
            }else if(con -> cp == GT){
                if(left > val->value.i){
                    return true;
                }
            }else{
                if(left == val->value.i){
                    return true;
                }
            }
        }else if (keys[i].flag == FIELD_DOUBLE){
            if(val->type != FIELD_DOUBLE){
                printf("can't match the type.\n");
                return error;
            }
            double left = * (double *)((char *) data + offset[i]);
            if(con->cp == LE){
                if(left <= val->value.d){
                    return true;
                }
            }else if(con -> cp == LT){
                if(left < val->value.d){
                    return true;
                }
            }else if(con -> cp == NE){
                if(left != val->value.d){
                    return true;
                }
            }else if(con -> cp == GE){
                if(left >= val->value.d){
                    return true;
                }
            }else if(con -> cp == GT){
                if(left > val->value.d){
                    return true;
                }
            }else{
                if(left == val->value.d){
                    return true;
                }
            }
        }else{
            if(val->type != FIELD_STRING){
                printf("can't match the type.\n");
                return error;
            }
            char * left = (char *) data + offset[i];

            if(con->cp == NE){
                if(strcmp(left,val->value.m_string)){
                    return true;
                }
            }else if(con->cp == EQ){
                if(!strcmp(left,val->value.m_string)){
                    return true;
                }
            }else{
                printf("error: wrong operation in the string type compare!\n");
                return error;
            }
        }

        return false;
    }else{
        if(con->cp == AND_CMP){
            bool left = selectItemMatch(keys,offset,data,number,con->left);
            if(left == error){
                return error;
            }
            if(left == false){
                return false;
            }
            bool right = selectItemMatch(keys,offset,data,number,con->right);
            if(right == error){
                return error;
            }

            if(right == false){
                return false;
            }

            return true;

        }
        if(con->cp == OR_CMP){
            bool left = selectItemMatch(keys,offset,data,number,con->left);
            if(left == error){
                return error;
            }
            if(left == true){
                return true;
            }
            bool right = selectItemMatch(keys,offset,data,number,con->right);
            if(right == error){
                return error;
            }

            if(right == true){
                return true;
            }

            return false;
        }
    }
}

SelectResultStructPoint idb_select_single_table(FILE * fp,SelectStructPoint ssp){
    SelectResultStructPoint  result = (SelectResultStructPoint) malloc(sizeof(SelectResultStruct));
    result->number = 0;
    result->data = NULL;

    FileHeaderStructPoint fhsp = idb_getFileHeader(fp);

    KeyStruct keys[fhsp->keyNumber];
    idb_readKeyStruct(fp,fhsp->keyNumber,keys);

    int offset[fhsp->keyNumber];

    offset[0] = 4;
    for(int i = 1;i < fhsp->keyNumber;i++){

        if(keys[i - 1].flag == FIELD_INT){
            offset[i] = offset[i - 1] + 4;
        }else if(keys[i - 1].flag == FIELD_DOUBLE){
            offset[i] = offset[i - 1] + 8;
        }else{
            offset[i] = offset[i - 1] +  keys[i - 1].length;
        }
    }

    void * data = malloc(fhsp->recordSize);

    SelectResultItemStructPoint tmp = NULL;

    fseek(fp,fhsp->headerLength,SEEK_SET);
    while(!feof(fp)){
        int readBack = fread(data,fhsp->recordSize,1,fp);
        if(readBack == 0){
            break;
        }
        if(* (int *) data == 0){
            bool res = selectItemMatch(keys,offset,data,fhsp->keyNumber,ssp->cp);
            if( res == error ){
                result->success = false;
                return result;
            }else if(res == true){

                if(result->data == NULL){
                    result->data = (SelectResultItemStructPoint) malloc(sizeof(struct SelectResultItem));
                    tmp = result->data;
                    tmp->space = malloc((fhsp->recordSize)  * 100);
                    tmp->next = NULL;
                    result->number = 1;
                    memcpy(tmp->space,(char *)data + 4,fhsp->recordSize - 4);
                }else{
                    int count = result->number;
                    if(count % 100 == 0) {
                        tmp->next = (SelectResultItemStructPoint) malloc(sizeof(struct SelectResultItem));
                        tmp = tmp->next;

                        tmp->space = malloc((fhsp->recordSize) * 100);
                        tmp->next = NULL;
                        result->number = 1;
                        memcpy(tmp->space,(char *)data + 4,fhsp->recordSize - 4);
                    }else{
                        int off = count % 100;
                        memcpy((char *)tmp->space + (fhsp->recordSize - 4) * off, (char *)data + 4, fhsp->recordSize - 4);
                        result->number += 1;
                    }
                }

            }
        }
    }

    result->success = true;

    return result;

}

ModifyResultStructPoint  idb_delete(FILE * fp,DeleteStructPoint dsp){
    ModifyResultStructPoint result = (ModifyResultStructPoint)malloc(sizeof(ModifyResultStruct));

    result->modifyNumber = 0;

    FileHeaderStructPoint fhsp = idb_getFileHeader(fp);

    KeyStruct keys[fhsp->keyNumber];
    idb_readKeyStruct(fp,fhsp->keyNumber,keys);

    int offset[fhsp->keyNumber];

    offset[0] = 4;
    for(int i = 1;i < fhsp->keyNumber;i++){

        if(keys[i - 1].flag == FIELD_INT){
            offset[i] = offset[i - 1] + 4;
        }else if(keys[i - 1].flag == FIELD_DOUBLE){
            offset[i] = offset[i - 1] + 8;
        }else{
            offset[i] = offset[i - 1] +  keys[i - 1].length;
        }
    }

    void * data = malloc(fhsp->recordSize);
    fseek(fp,fhsp->headerLength,SEEK_SET);

    int index = 0;

    while(!feof(fp)){
        int readBack = fread(data,fhsp->recordSize,1,fp);
        if(readBack == 0){
            break;
        }
        index += 1;
        if(* (int *) data == 0){
            bool res = selectItemMatch(keys,offset,data,fhsp->keyNumber,dsp->cp);
            if( res == error ){
                result->success = false;
                return result;
            }else if(res == true){

                if(fhsp->firstAvailableSpace == 0){
                    * (int *) data = -1;
                }else {
                    *(int *) data = fhsp->firstAvailableSpace;
                }

                fhsp->firstAvailableSpace = index;
                fseek(fp,-1 * fhsp->recordSize,SEEK_CUR);
                fwrite(data,fhsp->recordSize,1,fp);
                fhsp->recordNumber --;
                result->modifyNumber += 1;
            }
        }
    }

    idb_writeBackFileHeader(fp,fhsp);

    return result;
}

ModifyResultStructPoint idb_update(FILE * fp,UpdateStructPoint usp){
    ModifyResultStructPoint result = (ModifyResultStructPoint)malloc(sizeof(ModifyResultStruct));

    result->modifyNumber = 0;

    FileHeaderStructPoint fhsp = idb_getFileHeader(fp);

    KeyStruct keys[fhsp->keyNumber];
    idb_readKeyStruct(fp,fhsp->keyNumber,keys);

    int offset[fhsp->keyNumber];

    offset[0] = 4;
    for(int i = 1;i < fhsp->keyNumber;i++){

        if(keys[i - 1].flag == FIELD_INT){
            offset[i] = offset[i - 1] + 4;
        }else if(keys[i - 1].flag == FIELD_DOUBLE){
            offset[i] = offset[i - 1] + 8;
        }else{
            offset[i] = offset[i - 1] + keys[i-1].length;
        }
    }

    void * data = malloc(fhsp->recordSize);
    fseek(fp,fhsp->headerLength,SEEK_SET);

    int index = 0;

    while(!feof(fp)){
        int readBack = fread(data,fhsp->recordSize,1,fp);
        if(readBack == 0){
            break;
        }
        index += 1;
        if(* (int *) data == 0){
            bool res = selectItemMatch(keys,offset,data,fhsp->keyNumber,usp->cp);
            if( res == error ){
                result->success = false;
                return result;
            }else if(res == true){
                UpdatedFieldStructPoint  tmp = usp->fp;

                while(tmp != NULL){
                    int i = 0;
                    for(; i < fhsp->keyNumber;i++){
                        if(!strcmp(keys[i].name, tmp->name)){
                            break;
                        }
                    }
                    if(i == fhsp->keyNumber){
                        free(data);
                        free(fhsp);
                        printf("error: %s can not find the key in your fields!\n",tmp->name);
                        return false;
                    }

                    char * idx = (char *)data + offset[i];

                    if(keys[i].flag != tmp->value->type){
                        free(data);
                        free(fhsp);
                        printf("error: can not match the %d value with the key %s!\n",i,keys[i].name);
                        return false;
                    }
                    if(keys[i].flag == FIELD_INT){
                        *(int *)idx = tmp->value->value.i;
                    }else if(keys[i].flag == FIELD_DOUBLE){
                        *(double *)idx = tmp->value->value.d;
                    }else{
                        if(strlen(tmp->value->value.m_string) + 1 > keys[i].length){
                            free(data);
                            free(fhsp);
                            printf("error: the string '%s' in your values is too long which you defined the max length is %d in key %s!\n",
                                   tmp->value->value.m_string ,keys[i].length,keys[i].name);
                            return false;
                        }
                        strcpy(idx,tmp->value->value.m_string);
                    }
                    tmp = tmp->next;
                }
                fseek(fp,-1 * fhsp->recordSize,SEEK_CUR);
                fwrite(data,fhsp->recordSize,1,fp);
                result->modifyNumber += 1;
            }
        }
    }

    idb_writeBackFileHeader(fp,fhsp);

    return result;
}